package com.dongnam.quanly.common.controller;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.Collection;
import java.util.List;

import org.hibernate.Session;
import org.hibernate.Transaction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.dongnam.quanly.common.dao.GenericDao;
import com.dongnam.quanly.common.exceptions.DatabaseException;
import com.dongnam.quanly.common.persistence.HibernateUtil;

public class GenericController<T, ID extends Serializable> {

	private static final Logger logger = LoggerFactory.getLogger(GenericController.class);

	protected GenericDao<T, ID> dao;
	protected Class<T> pojoClazz;

	@SuppressWarnings("unchecked")
	public GenericController(GenericDao<T, ID> dao) {
		this.dao = dao;
		this.pojoClazz = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
	}

	public GenericDao<T, ID> getDao() {
		return dao;
	}

	public T getById(ID id, boolean lock) throws DatabaseException {
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			T pojo = dao.getById(id, lock);
			logger.debug("Controller:{}: getById = {}", pojoClazz.getSimpleName(), pojo);
			if (!isTransacted) {
				session.getTransaction().commit();
			}
			return pojo;
		} catch (Exception e) {
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	// DAO's findById returns a proxy that is not yet intialized. It is supposed
	// to be lazy-init in the session that create the proxy.
	public T findById(ID id, boolean lock) throws DatabaseException {
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			T pojo = dao.findById(id, lock);
			if (pojo != null)
				logger.debug("Controller:{}: findById = {}", pojoClazz.getSimpleName(), String.valueOf(pojo));
			if (!isTransacted) {
				session.getTransaction().commit();
			}
			return pojo;
		} catch (Exception e) {
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	public List<T> findAll(String sortby, String sorttype) {
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			List<T> pojos = dao.findAll(sortby, sorttype);
			logger.debug("Controller:{}: findAll = {}", pojoClazz.getSimpleName(), pojos);
			if (!isTransacted) {
				session.getTransaction().commit();
			}
			return pojos;
		} catch (Exception e) {
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	public List<T> findAll() throws DatabaseException {
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			List<T> pojos = dao.findAll();
			logger.debug("Controller:{}: findAll = {}", pojoClazz.getSimpleName(), pojos);
			if (!isTransacted) {
				session.getTransaction().commit();
			}
			return pojos;
		} catch (Exception e) {
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	public List<T> findByExample(T exampleInstance, String... excludeProperty) throws DatabaseException {
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			List<T> pojos = dao.findByExample(exampleInstance, excludeProperty);
			logger.debug("Controller:{}: findByExample = {}", pojoClazz.getSimpleName(), pojos);
			if (!isTransacted) {
				session.getTransaction().commit();
			}
			return pojos;
		} catch (Exception e) {
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	public T makePersistent(T entity) throws DatabaseException {
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			T pojo = dao.makePersistent(entity);
			logger.debug("Controller:{}: makePersistent = {}", pojoClazz.getSimpleName(), pojo);
			if (!isTransacted) {
				session.getTransaction().commit();
			}
			return pojo;
		} catch (Exception e) {
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	public void makeTransient(T entity) throws DatabaseException {
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			dao.makeTransient(entity);
			logger.debug("Controller:{}: makeTransient() ", pojoClazz.getSimpleName());
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Exception e) {
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	public void flush() throws DatabaseException {
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			dao.flush();
			logger.debug("Controller:{}: flush() ", pojoClazz.getSimpleName());
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Exception e) {
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	public void clear() throws DatabaseException {
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			dao.clear();
			logger.debug("Controller:{}: clear() ", pojoClazz.getSimpleName());
			if (!isTransacted) {
				session.getTransaction().commit();
			}
		} catch (Exception e) {
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	public void batchPersist(Collection<T> batch) {
		logger.debug(this.getClass().getSimpleName() + ".batchPersist start");
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			for (T entity : batch) {
				session.persist(entity);
			}
			if (!isTransacted) {
				session.getTransaction().commit();
			}
			logger.debug(this.getClass().getSimpleName() + ".batchPersist end");
		} catch (Exception e) {
			logger.error("batchPersist", e);
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	public void batchSaveOrUpdate(Collection<T> batch) {
		logger.debug(this.getClass().getSimpleName() + ".batchMerge start");
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			for (T entity : batch) {
				session.saveOrUpdate(entity);
			}
			if (!isTransacted) {
				session.getTransaction().commit();
			}
			logger.debug(this.getClass().getSimpleName() + ".batchMerge end");
		} catch (Exception e) {
			logger.error("batchMerge", e);
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	public void batchPersist(T[] batch) {
		logger.debug(this.getClass().getSimpleName() + ".batchPersist start");
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			for (T entity : batch) {
				session.persist(entity);
			}
			if (!isTransacted) {
				session.getTransaction().commit();
			}
			logger.debug(this.getClass().getSimpleName() + ".batchPersist end");
		} catch (Exception e) {
			logger.error("batchPersist", e);
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	// Actually this might not work well as an inserting save flushes session
	// immediately
	public void batchSave(Collection<T> batch) {
		logger.debug(this.getClass().getSimpleName() + ".batchSave start");
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			for (T entity : batch) {
				session.saveOrUpdate(entity);
			}
			if (!isTransacted) {
				session.getTransaction().commit();
			}
			logger.debug(this.getClass().getSimpleName() + ".batchSave end");
		} catch (Exception e) {
			logger.error("batchSave", e);
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	public void batchDelete(Collection<T> batch) {
		logger.debug(this.getClass().getSimpleName() + ".batchDelete start");
		boolean isTransacted = HibernateUtil.getCurrentSession().getTransaction().isActive();
		Session session = isTransacted ? HibernateUtil.getCurrentSession() : HibernateUtil.startCurrentSession();
		try {
			for (T entity : batch) {
				session.delete(entity);
			}
			if (!isTransacted) {
				session.getTransaction().commit();
			}
			logger.debug(this.getClass().getSimpleName() + ".batchDelete end");
		} catch (Exception e) {
			logger.error("batchDelete", e);
			rollbackConditionally(isTransacted, session);
			throw new DatabaseException(e);
		}
	}

	protected void rollbackConditionally(boolean isTransacted, Session session) {
		if (!isTransacted && session.isOpen() && !session.getTransaction().wasCommitted())
			session.getTransaction().rollback();
	}
}
